package gpsm2

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"gitee.com/gjing1st/gopackage/utils/gprand"
	"github.com/tjfoc/gmsm/sm2"
	"math/big"
	"syscall"
)

const TEMP_KEY_MAGIC = "FRTYPzAZGgMOdqEVk5DNthu5"
const SOFT_KEY_MAGIC = "MaeCycTh4TVrTeQbkuzjPCCB"
const TEMP_KEY_PIN = "\x0e\xdf\x32\x68\x55\xea\xba\xd3"

/* 说明：
 * PubKeyStr代表密钥对的公钥，非压缩格式的Hex编码
 * PrvKeyStr代表密钥对的私钥，经过SM4_ECB加密之后的密文的Hex编码
 * Salt用于恢复加密私钥的对称密钥的材料，16字节二进制的Hex编码
 * 对称密钥恢复方法：SM3_HASH(SOFT_KEY_MAGIC || PIN || Salt)[8:24]
 */
type SoftPrivateKey struct {
	PubKeyStr string `json:"pubkey"`
	PrvKeyStr string `json:"prvkey"`
	Salt      string `json:"salt"`
	prvKey    *sm2.PrivateKey
}
type TempPrivateKey sm2.PrivateKey

// GenerateSoftKey
// @description: 生成密钥对
// @param: pin随机字符串 SOFT_KEY_MAGIC
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2022/1/7 16:45
// @success:
func GenerateSoftKey(pin string) (prvkey *SoftPrivateKey, strpriv string, err error) {
	if len(pin) < 6 {
		err = syscall.EINVAL
		return
	}
	fmt.Println("----------------")
	priv, err := sm2.GenerateKey(rand.Reader)
	tpriv := (*TempPrivateKey)(priv)
	strpriv, err = tpriv.Encode()
	if err != nil {
		// fmt.Printf("priv D len:%d\n", len(priv.D.Bytes()))
		return
	}
	pubKey, _ := priv.Public().(*sm2.PublicKey)
	pubkeyBytes := make([]byte, 65)
	x, y := pubKey.X.Bytes(), pubKey.Y.Bytes()
	copy(pubkeyBytes[33-len(x):33], x)
	copy(pubkeyBytes[65-len(y):], y)
	pubkeyBytes[0] = 0x04

	salt := gprand.S(16, false)
	var cipherPrv [32]byte
	dbuf := priv.D.Bytes()
	copy(cipherPrv[32-len(dbuf):], dbuf)
	symmEcbEncrypt(generateSymmKey(SOFT_KEY_MAGIC, pin, salt), cipherPrv[:], cipherPrv[:])

	prvkey = &SoftPrivateKey{
		PubKeyStr: hex.EncodeToString(pubkeyBytes),
		PrvKeyStr: hex.EncodeToString(cipherPrv[:]),
		Salt:      salt,
		prvKey:    priv,
	}
	return
}

// Sm2Sign
// @description: 使用sign签名后转十六进制为140~144位，无法与python联调，改用sm2sign签名，加密后规定长度128位
// @param: msg 需要签名的消息体
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2021/12/15 15:46
// @success:
func (spk *SoftPrivateKey) Sm2Sign(msg []byte) ([]byte, error) {
	r, s, err := sm2.Sm2Sign(spk.prvKey, msg, nil, nil) // 签名
	var buffer bytes.Buffer

	buffer.Write(r.Bytes())

	buffer.Write(s.Bytes())

	return buffer.Bytes(), err

}

func (spk *SoftPrivateKey) Sm2SignDigit(msg []byte) (signature []byte, err error) {
	r, s, err := sm2.Sm2Sign(spk.prvKey, msg, nil, nil) // 签名
	if err != nil {
		return
	}
	signature, err = sm2.SignDigitToSignData(r, s)
	return
}

// TempToSoftKey
// @description: 临时密钥转soft类型密钥
// @param:
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2022/1/4 17:54
// @success:
func TempToSoftKey(priv *sm2.PrivateKey, pin string) (prvkey *SoftPrivateKey, err error) {
	if len(pin) < 6 {
		err = syscall.EINVAL
		return
	}
	// fmt.Printf("priv D len:%d\n", len(priv.D.Bytes()))
	pubKey, _ := priv.Public().(*sm2.PublicKey)
	pubkeyBytes := make([]byte, 65)
	x, y := pubKey.X.Bytes(), pubKey.Y.Bytes()
	copy(pubkeyBytes[33-len(x):33], x)
	copy(pubkeyBytes[65-len(y):], y)
	pubkeyBytes[0] = 0x04

	salt := gprand.S(16, false)
	var cipherPrv [32]byte
	dbuf := priv.D.Bytes()
	copy(cipherPrv[32-len(dbuf):], dbuf)
	symmEcbEncrypt(generateSymmKey(SOFT_KEY_MAGIC, pin, salt), cipherPrv[:], cipherPrv[:])

	prvkey = &SoftPrivateKey{
		PubKeyStr: hex.EncodeToString(pubkeyBytes),
		PrvKeyStr: hex.EncodeToString(cipherPrv[:]),
		Salt:      salt,
		prvKey:    priv,
	}
	return
}

func (tpk *TempPrivateKey) Encode() (encode string, err error) {
	if tpk == nil || tpk.D == nil {
		err = syscall.EINVAL
		return
	}
	salt := gprand.S(16, false)
	d := make([]byte, 113)
	dbytes := tpk.D.Bytes()
	copy(d[32-len(dbytes):32], dbytes)
	symmEcbEncrypt(generateSymmKey(TEMP_KEY_MAGIC, TEMP_KEY_PIN, salt), d[:32], d[65:97])
	x := tpk.PublicKey.X.Bytes()
	y := tpk.PublicKey.Y.Bytes()
	copy(d[33-len(x):33], x)
	copy(d[65-len(y):65], y)
	d[0] = 0x04
	copy(d[97:], []byte(salt))
	encode = hex.EncodeToString(d)
	return
}

// Sm2Verify
// @description: 验证签名
// @param:
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2022/1/7 17:30
// @success:
func (spk *SoftPrivateKey) Sm2Verify(msg, sign []byte) bool {
	r := new(big.Int).SetBytes(sign[:32])
	s := new(big.Int).SetBytes(sign[32:])
	b := sm2.Sm2Verify(&spk.prvKey.PublicKey, msg, nil, r, s) // 私钥验签

	return b
}
